package cn.jbooter.coder.generator;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import cn.jbooter.coder.helper.GenerateHelper;
import cn.jbooter.coder.model.CmdLine;
import cn.jbooter.coder.util.*;

/**
 * 代码生成处理器
 * 
 * @author HeJian
 *
 */
public class Generator {

	// ######## jdbc等基本信息
	// 数据库类型
	private String dbType = "";
	private String driver = "";
	private String url = "";
	private String username = "";
	private String password = "";
	// ######## generator 基本信息
	private String prepackage = ""; // 包前缀
	private String subpackage = ""; // 子包(子模块)
	private List<String> layers = new ArrayList<>(); // 要生成的层: service,dao
	private String output = ""; // 代码要生成在哪个路径

	/** 表名称 **/
	private String table = "";
	/** 表主键,多个主键用,号隔开 **/
	private String pks = "";
	/** 表注释 **/
	private String tableRemark = "";
	/** 所有的列名称 **/
	private List<String> colNames = new ArrayList<String>();
	/** 表字段类型 **/
	private List<String> colTypes = new ArrayList<String>();
	/** 表字段的宽度 **/
	private List<Integer> colSizes = new ArrayList<Integer>();
	/** 表字段是否允许为 NULL **/
	private List<Integer> colNullables = new ArrayList<Integer>();
	/** 表字段注释 **/
	private List<String> colRemarks = new ArrayList<String>();

	/** 自增主键字段: key 表列名 value: YES **/
	private Map<String, String> idAutoIncrement = new HashMap<String, String>();
	/** 主键的java类型 key为主键名称转java驼峰的名称, value为java类型 **/
	private Map<String, String> pkJavaTyps = new HashMap<String, String>();

	/** 基包=prepackage+subpackage **/
	private String basePackage = "";
	/** api完整包名称 **/
	private String packageApi = "";
	/** mapper接口完整包名称 **/
	private String packageMapper = "";
	/** model完整包名称 **/
	private String packageModel = "";
	/** service接口完整包名称 **/
	private String packageService = "";

	/** model类名 **/
	private String model = "";
	/** api类名 **/
	private String api = "";
	/** mapper类名 **/
	private String mapper = "";
	/** service类名 **/
	private String service = "";

	/*
	 * 生成代码
	 * 
	 * @param genInfo
	 */
	public static void generate(CmdLine genInfo) {
		try {
			System.out.println("===>>>开始生成代码!");
			// 数据项校验
			if (genInfo == null) {
				throw new RuntimeException("代码生成参数为空!");
			}
			String _dbType = genInfo.getDbType();
			String _driver = genInfo.getDriver();
			String _url = genInfo.getUrl();
			String _username = genInfo.getUsername();
			String _password = genInfo.getPassword();
			String _prepackage = genInfo.getPrepackage();
			String _subpackage = genInfo.getSubpackage();
			List<String> _layers = genInfo.getLayers();
			String _output = genInfo.getOutput();
			List<String> _tables = genInfo.getTables();
			// 参数校验
			if (StringUtil.isEmpty(_url)) {
				throw new RuntimeException("--url参数为空!");
			}
			if (StringUtil.isEmpty(_dbType)) {
				throw new RuntimeException("解析到dbType为空:url" + _url);
			}
			if (StringUtil.isEmpty(_driver)) {
				throw new RuntimeException("解析道driver为空:url:" + _url);
			}
			if (StringUtil.isEmpty(_username)) {
				throw new RuntimeException("--username参数为空!");
			}
			if (StringUtil.isEmpty(_password)) {
				throw new RuntimeException("--password参数为空!");
			}
			if (StringUtil.isEmpty(_prepackage)) {
				throw new RuntimeException("--prepackage参数为空!");
			}
			if (StringUtil.isEmpty(_subpackage)) {
				throw new RuntimeException("--subpackage参数为空!");
			}
			if (StringUtil.isEmpty(_output)) {
				throw new RuntimeException("--output参数为空!");
			}
			if (_layers == null || _layers.size() <= 0) {
				throw new RuntimeException("--layers参数为空!");
			}
			if (_tables == null || _tables.size() <= 0) {
				throw new RuntimeException("--tables参数为空!");
			}

			// 循环依次生成每张表的代码
			for (String _table : _tables) {
				if (_table == null || _table.trim().length() == 0)
					throw new RuntimeException("--tables参数有误!");
				_table = _table.trim();
				Generator gh = new Generator(_dbType, _driver, _url, _username, _password, _prepackage,
						_subpackage, _layers, _table, _output);
				// 连接数据库
				gh.connectDb();
				// 执行生成代码
				gh.exec();
			}
			System.out.println("<<<===代码生成完成!");
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println(e.getMessage());
		}
	}

	/**
	 * 构造函数
	 * 
	 * @param url
	 * @param username
	 * @param password
	 * @param prepackage
	 * @param subpackage
	 * @param layers
	 * @param table
	 * @param output
	 */
	public Generator(String dbType, String driver, String url, String username, String password,
			String prepackage, String subpackage, List<String> layers, String table, String output) {
		// 组装基础数据
		this.dbType = dbType;
		this.driver = driver.trim();
		this.url = url.trim();
		this.username = username.trim();
		this.password = password.trim();
		this.prepackage = prepackage.trim();
		this.subpackage = subpackage.trim();
		this.layers = layers;
		this.table = table.toUpperCase().trim();
		this.output = output.trim();

		// 解析其它参数
		this.basePackage = this.prepackage + (StringUtil.isEmpty(this.subpackage) ? "" : "." + this.subpackage);
		this.packageApi = this.basePackage + ".api";
		this.packageMapper = this.basePackage + ".mapper";
		this.packageModel = this.basePackage + ".model";
		this.packageService = this.basePackage + ".service";

		this.model = GenerateHelper.getEntityName(this.table).trim();
		this.api = this.model + "Api";
		this.mapper = this.model + "Mapper";
		this.service = this.model + "Service";
	}

	/**
	 * 连接数据库,并解析表meta-data到相关参数
	 */
	public void connectDb() {
		// 连接数据库
		Connection con = null;
		String sql = null;
		if ("oracle".equalsIgnoreCase(this.dbType)) {
			sql = "SELECT * FROM " + this.table + " where rownum < 2";
		} else if ("mysql".equalsIgnoreCase(this.dbType)) {
			sql = "SELECT * FROM " + this.table + " LIMIT 1";
		} else {
			throw new RuntimeException("dbType解析错误!");
		}
		Statement pStemt = null;
		try {
			Class.forName(this.driver);

			Properties props = new Properties();
			props.put("user", this.username);
			props.put("password", this.password);
			props.put("remarksReporting", "true");
			con = DriverManager.getConnection(this.url, props);

			pStemt = (Statement) con.createStatement();
			ResultSet rs = pStemt.executeQuery(sql);
			ResultSetMetaData rsmd = rs.getMetaData();
			DatabaseMetaData metaData = con.getMetaData();
			ResultSet r = metaData.getPrimaryKeys(null, "", this.table);
			// 获取主键信息
			while (r.next()) {
				String columnName = r.getString("COLUMN_NAME");
				this.pks = this.pks + columnName + ",";
			}
			if (this.pks.trim().length() == 0) {
				System.err.println("<<<===警告:该表没有主键!");
			} else {
				this.pks = this.pks.substring(0, this.pks.length() - 1);
			}
			ResultSet colRet = metaData.getColumns(null, "%", this.table, "%");
			while (colRet.next()) {
				String columnName = colRet.getString("COLUMN_NAME");
				// 列字段的注释
				this.colRemarks.add(colRet.getString("REMARKS"));
				// id是否是自增
				String isAutoincrement = colRet.getString("IS_AUTOINCREMENT");
				if ("YES".equalsIgnoreCase(isAutoincrement)) {
					// 如果自增主键,给个自增标记
					this.idAutoIncrement.put(columnName.trim(), "YES");// GenerateHelper.getAttrName(columnName)
				}
			}

			// 获取表中列信息
			int size = rsmd.getColumnCount(); // 列数目
			for (int i = 0; i < size; i++) {
				// 列名
				this.colNames.add(rsmd.getColumnName(i + 1));
				// 字段类型
				this.colTypes.add(rsmd.getColumnTypeName(i + 1));
				// 字段允许的宽度
				this.colSizes.add(rsmd.getColumnDisplaySize(i + 1));
				// 字段是否允许为NULL
				this.colNullables.add(rsmd.isNullable(i + 1));
				// 获取主键类型
				if (this.pks.indexOf(this.colNames.get(i)) != -1) {
					String attKey = GenerateHelper.getAttrName(this.colNames.get(i)); // 属性名称
					String typeValue = GenerateHelper.sqlType2JavaType(this.colTypes.get(i)); // java类型
					this.pkJavaTyps.put(attKey, typeValue);
				}
			}

			// 表名的注释
			String tc_sql = null;
			if ("oracle".equalsIgnoreCase(this.dbType)) {
				tc_sql = "select comments from user_tab_comments where table_name = '" + this.table + "'";
				ResultSet trs = pStemt.executeQuery(tc_sql);
				if (trs != null && trs.next()) {
					String comments = trs.getString(1);
					this.tableRemark = comments;
				}
			} else if ("mysql".equalsIgnoreCase(this.dbType)) {
				tc_sql = "SHOW CREATE TABLE " + this.table;
				ResultSet trs = pStemt.executeQuery(tc_sql);
				if (trs != null && trs.next()) {
					String create = trs.getString(2);
					this.tableRemark = GenerateHelper.getTableCommet(create);
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		} finally {
			try {
				con.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 根据table执行生成java代码
	 */
	public void exec() {
		//先生成model模型层
		this.generateModel();
		//生成其它层级
		for(int i=0 ; i<this.layers.size();i++) {
			String layer = this.layers.get(i).trim();
			if("service".equalsIgnoreCase(layer)) {
				//生成api接口service实现
				this.generateService();
			}else if("dao".equalsIgnoreCase(layer)){
				//生成dao层
				this.generateMapper();
			}else if("dubbo".equalsIgnoreCase(layer)) {
				//生成dubbo的xml文件
				this.generateDubboProviderXml();
			}
		}
	}



	/**
	 * 生成dubbo提供者的xml配置文件
	 */
	private void generateDubboProviderXml() {
		Map<String, Object> content = new HashMap<String, Object>();
		content.put("packageApi", this.packageApi);
		content.put("api", this.api);
		content.put("serviceRef", StringUtil.toFirstLower(this.service));
		

		// 生成dubboxml
		String dubboCodeStr = FreeMarkUtil.executeUTF8("template/dubbo-provider-xml.ftl/", content);
		String dubboFileName = "dubbo-"+StringUtil.toFirstLower(this.service)+".provider.xml";
		String dubboXmlFilePath = this.output.endsWith("/") ? this.output + "config/dubbo/"
				: this.output + "/config/dubbo/";
		if ( !StringUtil.isEmpty(this.subpackage)) {
			dubboXmlFilePath = dubboXmlFilePath + GenerateHelper.packageToPath(this.subpackage);
		}
		// 写入到文件
		FileUtil.writeFile(dubboCodeStr, dubboFileName, dubboXmlFilePath);

		
	}

	/**
	 * 生成api和service实现类
	 */
	private void generateService() {
		Map<String, Object> content = new HashMap<String, Object>();
		content.put("packageApi", this.packageApi);
		content.put("packageModel", this.packageModel);
		content.put("model", this.model);
		content.put("modelVal", StringUtil.toFirstLower(this.model));
		content.put("service", this.service);
		content.put("packageService", this.packageService);
		content.put("packageMapper", this.packageMapper);
		content.put("mapper", this.mapper);
		content.put("mapperVal", StringUtil.toFirstLower(this.mapper));
		content.put("api", this.api);

		// 生成api接口
		String apiCodeStr = FreeMarkUtil.executeUTF8("template/api.ftl/", content);
		String apiFileName = this.api + ".java";
		String apiFilePath = this.api.endsWith("/") ? this.output + GenerateHelper.packageToPath(this.packageApi)
				: this.output + "/" + GenerateHelper.packageToPath(this.packageApi);
		// 写入到文件
		FileUtil.writeFile(apiCodeStr, apiFileName, apiFilePath);

		// 生成service实现类
		String serviceCodeStr = FreeMarkUtil.executeUTF8("template/service.ftl/", content);
		String serviceFileName = this.service + ".java";
		String serviceFilePath = this.output.endsWith("/")
				? this.output + GenerateHelper.packageToPath(this.packageService)
				: this.output + "/" + GenerateHelper.packageToPath(this.packageService);
		// 写入到文件
		FileUtil.writeFile(serviceCodeStr, serviceFileName, serviceFilePath);
	}

	/**
	 * 生成mapper和mapper.xml
	 */
	private void generateMapper() {
		Map<String, Object> content = new HashMap<String, Object>();
		content.put("packageMapper", this.packageMapper);
		content.put("packageModel", this.packageModel);
		content.put("mapper", this.mapper);
		content.put("model", this.model);
		content.put("table", this.table);
		// mapper xml 字段映射
		List<Map<String, Object>> mappingList = new ArrayList<Map<String, Object>>();
		for (int i = 0; i < this.colNames.size(); i++) {
			Map<String, Object> mapping = new HashMap<String, Object>();
			mapping.put("columnName", this.colNames.get(i)); // 列名称
			mapping.put("fieldName", GenerateHelper.getAttrName(this.colNames.get(i))); // java属性名称
			mapping.put("javaType", GenerateHelper.sqlType2JavaType(this.colTypes.get(i))); // java类型
			mappingList.add(mapping);
		}
		content.put("mappingList", mappingList);

		// 生成mapper接口
		String mapperCodeStr = FreeMarkUtil.executeUTF8("template/mapper.ftl/", content);
		String mapperFileName = this.mapper + ".java";
		String mapperFilePath = this.output.endsWith("/") ? this.output + GenerateHelper.packageToPath(this.packageMapper)
				: this.output + "/" + GenerateHelper.packageToPath(this.packageMapper);
		// 写入到文件
		FileUtil.writeFile(mapperCodeStr, mapperFileName, mapperFilePath);

		// 生成mapper xml文件
		String mapperXmlCodeStr = FreeMarkUtil.executeUTF8("template/mapper-xml.ftl/", content);
		String mapperXmlFileName = StringUtil.toFirstLower(this.model) + "-mapper.xml";
		String mapperXmlFilePath = this.output.endsWith("/") ? this.output + "config/mapper/"
				: this.output + "/config/mapper/";
		if ( !StringUtil.isEmpty(this.subpackage)) {
			mapperXmlFilePath = mapperXmlFilePath + GenerateHelper.packageToPath(this.subpackage);
		}
		// 写入到文件
		FileUtil.writeFile(mapperXmlCodeStr, mapperXmlFileName, mapperXmlFilePath);
	}

	/**
	 * 生成model
	 */
	private void generateModel() {
		Map<String, Object> content = new HashMap<String, Object>();
		// 包路径
		content.put("packageModel", this.packageModel);
		// 表注释
		content.put("tableRemark", this.tableRemark);
		// 表名称
		content.put("table", this.table);
		// 类名
		content.put("model", this.model);
		// 属性列表
		List<Map<String, Object>> attInfoList = new ArrayList<Map<String, Object>>();
		for (int i = 0; i < this.colNames.size(); i++) {
			Map<String, Object> attInfo = new HashMap<String, Object>();
			// 属性注释
			attInfo.put("comment", this.colRemarks.get(i));
			attInfo.put("javaType", GenerateHelper.sqlType2JavaType(this.colTypes.get(i)));
			attInfo.put("fieldName", GenerateHelper.getAttrName(this.colNames.get(i)));
			attInfo.put("fieldMethod", StringUtil.toFirstUpper(GenerateHelper.getAttrName(this.colNames.get(i))));
			// 注解列表组装
			List<String> annotations = new ArrayList<String>();
			if (pks.indexOf(this.colNames.get(i)) != -1) {
				// id注解
				annotations.add("@Id");
				String increFlag = this.idAutoIncrement.get(this.colNames.get(i));
				if ("YES".equals(increFlag)) {
					// 自增属性注解
					annotations.add("@GeneratedValue(generator = \"JDBC\")");
				}
			} else {
				// 一般注解
				annotations.add("@Column(name=\"" + this.colNames.get(i) + "\")");
			}
			attInfo.put("annotations", annotations);

			attInfoList.add(attInfo);
		}
		content.put("attInfoList", attInfoList);

		// 生成model代码
		String codeStr = FreeMarkUtil.executeUTF8("template/model.ftl/", content);
		// 写入到文件
		String fileName = this.model + ".java";
		String filePath = this.output.endsWith("/") ? this.output + GenerateHelper.packageToPath(this.packageModel)
				: this.output + "/" + GenerateHelper.packageToPath(this.packageModel);
		FileUtil.writeFile(codeStr, fileName, filePath);
	}

}
